数据结构基础

1 数据结构分类

线性结构:把所有节点用一根线串起来

顺序表与链表

非线性结构

树与图

2 线性结构

顺序表与链表的区别

顺序表需要一块连续的内存空间来存储,对内存的要求比较高。如果我们申请一个 100MB 大小的顺序表,当内存中没有连续的、足够大的存储空间时,即便内存的剩余总可用空间大于 100MB,仍然会申请失败。 而链表恰恰相反,它并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用,所以如果我们申请的是 100MB 大小的链表,根本不会有问题。

连续存储(顺序表)

对于顺序表来说,是要开辟一个连续的内存空间

假设有一个顺序表[1,2,3,4],其起始地址为1000(如上图),要找到第三个元素(下标为2),表达式为:第三个元素的起始地址为:1000+(2*4)

在python中列表就是一种顺序表,它提供了哪些操作?

对于这些操作的时间复杂度是多少?

比如,

找到下标为n的元素,其时间复杂度为:O(1)

增删改来说,其时间复杂度就为:O(n)(是一种连续的空间,增删改都会使其它元素地址发生改变)

为啥顺序表(列表)的索引或者下标是从0开始的?

假设是从1开始的,找到上面第三个元素(下标为3),表达式为:1000+(4*(3-1)),相比于下标为0开始,多做了一次运算

关于顺序表的优缺点

  • 优点:
    • 存取速度快
  • 缺点:
    • 事先需要知道数组的长度

    •  

      需要大块的连续内存

    •  

      插入删除非常的慢,效率极低

离散存储(链表)

定义:

1.n个节点离散分配

2.通过指针相互关联

关于链表的优缺点

空间没有限制,插入删除元素很快

查询比较慢

链表的节点的结构如下

 

data为自定义的数据,next为下一个节点的地址。

链表的分类

单向链表

双向链表

循环链表

链表实现

在python中没有指针的概念,我们使用类来实现链表

# 单链表的实现

class Node():
    def __init__(self, ele):
        self.ele = ele
        self.next = None


class SingleLinkList:
    def __init__(self, node=None):
        # 指向头部节点
        self.__head = node

    def is_empty(self):
        """
        判断链表是否为空
        :return: bool
        """
        return not self.__head

    def length(self):
        """
        返回列表长度
        :return: int
        """
        cur = self.__head
        ctn = 0
        while cur is not None:
            ctn += 1
            cur = cur.next
        return ctn

    def travel(self):
        """
        遍历链表
        :return: val
        """
        cur = self.__head
        while cur is not None:
            print(cur.ele, end=" ")
            cur = cur.next
        print("--")

    def append(self, ele):
        """在末尾添加元素"""

        node = Node(ele)
        if self.is_empty():
            self.__head = node
        else:
            cur = self.__head
            while cur.next is not None:
                cur = cur.next
            cur.next = node

    def add(self, item):
        """在头部添加元素"""
        node = Node(item)
        node.next = self.__head
        self.__head = node

    def remove(self, item):
        """
        删除指定元素
        :param item: 
        :return: 
        """
        pre = None
        cur = self.__head
        while cur is not None:
            if cur.ele == item:
                if pre is None:
                    self.__head = cur.next
                else:
                    pre.next = cur.next
                break
            else:
                pre = cur
                cur = cur.next


    def search(self, item):
        """检索检点是否存在"""
        cur = self.__head
        while cur is not None:
            if cur.ele == item:
                return True
            else:
                cur = cur.next
        return False


    def insert(self, pos, item):
        """
        在指定位置插入元素
        :param pos: 
        :param item: 
        :return: 
        """
        if pos <= 0:
            self.add(item)
        elif pos >= self.length():
            self.append(item)
        else:
            ctn = 0
            cur = self.__head
            while ctn < pos - 1:
                ctn += 1
                cur = cur.next
            node = Node(item)
            node.next = cur.next
            cur.next = node



if __name__ == '__main__':
    ll = SingleLinkList()
    ll.append(1)
    ll.append(2)
    # ll.append(3)
    ll.travel()
    ll.remove(1)
    ll.travel()
单链表
# 单向循环链表实现

class Node():
    def __init__(self, ele):
        self.ele = ele
        self.next = None


class SingleLinkList:
    def __init__(self, node=None):
        # 指向头部节点
        self.__head = node
        if node:
            node.next = node

    def is_empty(self):
        return not self.__head

    def length(self):

        cur = self.__head
        if self.is_empty():
            return 0
        ctn = 1
        while cur.next != self.__head:
            ctn += 1
            cur = cur.next
        return ctn

    def travel(self):
        """
        遍历链表
        :return: val
        """
        if self.is_empty():
            return False
        cur = self.__head
        while cur.next != self.__head:
            print(cur.ele, end=" ")
            cur = cur.next
        print(cur.ele)

    def append(self, ele):
        """在末尾添加元素"""

        node = Node(ele)
        if self.is_empty():
            self.__head = node
            node.next = node
        else:
            cur = self.__head
            while cur.next != self.__head:
                cur = cur.next
            cur.next = node
            node.next = self.__head

    def add(self, item):
        """在头部添加元素"""
        node = Node(item)
        if self.is_empty():
            node.next = node
            self.__head = node
        else:
            cur = self.__head
            while cur.next != self.__head:
                cur = cur.next
            node.next = self.__head
            self.__head = node
            cur.next = self.__head

    def remove(self, item):
        pre = None
        cur = self.__head
        if self.is_empty():
            return
        while cur.next != self.__head:
            if cur.ele == item:
                # 头节点
                if cur == self.__head:
                    rear = self.__head
                    # 得到尾结点
                    while rear.next != self.__head:
                        rear = rear.next
                    self.__head = cur.next
                    rear.next = self.__head
                # 中间节点
                else:
                    pre.next = cur.next
                return
            else:
                pre = cur
                cur = cur.next
        # 最后一个节点
        if cur.ele == item:
            if cur.next == cur:
                self.__head = None
            else:
                pre.next = self.__head

    def search(self, item):
        """检索检点是否存在"""
        cur = self.__head
        while cur.next != self.__head:
            if cur.ele == item:
                return True
            else:
                cur = cur.next
        return False

    def insert(self, pos, item):
        if pos <= 0:
            self.add(item)
        elif pos >= self.length():
            self.append(item)
        else:
            ctn = 0
            pre = self.__head
            while ctn < pos - 1:
                ctn += 1
                pre = pre.next
            node = Node(item)
            node.next = pre.next
            pre.next = node


if __name__ == '__main__':
    ll = SingleLinkList()
    ll.insert(0,1)
    ll.travel()
单向循环链表
# 双链表的实现
from single_link import SingleLinkList


class Node():
    def __init__(self, ele):
        self.ele = ele
        self.pre = None
        self.next = None


class DoubleLinkList(SingleLinkList):

    def append(self, ele):
        """在末尾添加元素"""

        node = Node(ele)
        if self.is_empty():
            self._SingleLinkList__head = node
        else:
            cur = self._SingleLinkList__head
            while cur.next is not None:
                cur = cur.next
            cur.next = node
            node.pre = cur

    def add(self, item):
        """在头部添加元素"""
        node = Node(item)
        node.next = self._SingleLinkList__head
        if not self.is_empty():
            self._SingleLinkList__head.pre = node
        self._SingleLinkList__head = node

    def remove(self, item):
        cur = self._SingleLinkList__head
        while cur is not None:
            if cur.ele == item:
                # 判断是否为头节点
                if cur == self._SingleLinkList__head:
                    self._SingleLinkList__head = cur.next
                    # 判断是否只有一个节点
                    if cur.next:
                        cur.next.pre = None
                else:
                    cur.pre.next = cur.next
                    # 判断是否为尾结点
                    if cur.next:
                        cur.next.pre = cur.pre
                break
            else:
                cur = cur.next

    def insert(self, pos, item):
        if pos <= 0:
            self.add(item)
        elif pos >= self.length():
            self.append(item)
        else:
            ctn = 0
            cur = self._SingleLinkList__head
            while ctn < pos - 1:
                ctn += 1
                cur = cur.next
            node = Node(item)
            node.next = cur.next
            node.pre = cur
            cur.next = node
            if cur.next:
                cur.next.pre = node


if __name__ == '__main__':
    ll = DoubleLinkList()
双链表

约瑟夫问题

设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,求出队列中最后的一个人或者出队编号的序列

class Node:
    def __init__(self, ele):
        self.ele = ele
        self.next = None


class SingleLinkList:
    def __init__(self, node=None):
        self.__head = node
        if node:
            node.next = node

    def length(self):
        cur = self.__head
        if self.is_empty():
            return 0
        ctn = 1
        while cur.next != self.__head:
            ctn += 1
            cur = cur.next
        return ctn

    def is_empty(self):
        return not self.__head

    def append(self, ele):
        node = Node(ele)
        if self.is_empty():
            self.__head = node
            node.next = node
        else:
            cur = self.__head
            while cur.next != self.__head:
                cur = cur.next
            cur.next = node
            node.next = self.__head

    def travel(self):
        if self.is_empty():
            return False
        cur = self.__head
        while cur.next != self.__head:
            print(cur.ele, end=" ")
            cur = cur.next
        print(cur.ele)

    def find_ele(self, num, station=1):

        while station > 1:
            self.__head = self.__head.next
            station -= 1

        first = None
        second = self.__head

        while self.length() != 1:
            print("当前链表值:", end="")
            self.travel()
            print("链表长度:", self.length())
            ctn = 1
            while ctn < num:
                first = second
                second = second.next
                ctn += 1
            print("删除的元素是:", second.ele)
            if self.__head.ele == second.ele:
                self.__head = second.next
            first.next = second.next
            second = second.next


if __name__ == '__main__':
    obj = SingleLinkList()
    for i in range(1, 101):
        obj.append(i)
    obj.find_ele(150, 20)
    print("最后的孩子:", end="")
    obj.travel()  
方式一
class Child(object):
    first = None
    def __init__(self, no = None, pNext = None):
        self.no = no
        self.pNext = pNext
    def addChild(self, n=4):
        cur = None
        for i in range(n):
            child = Child(i + 1)
            if i == 0:
                self.first = child
                self.first.pNext = child
                cur = self.first
            else:
                cur.pNext = child
                child.pNext = self.first
                cur = cur.pNext
    def showChild(self):
        cur = self.first
        while cur.pNext != self.first:
            print("小孩的编号是:%d" % cur.no)
            cur = cur.pNext
        print("小孩的编号是: %d" % cur.no)
    def countChild(self, m, k):
        tail = self.first
        while tail.pNext != self.first:
            tail = tail.pNext
        # 出来后,已经是在first前面
        # 从第几个人开始数
        for i in range(k-1):
            tail = tail.pNext
            self.first = self.first.pNext
        # 数两下,就是让first和tail移动一次
        # 数三下,就是让first和tail移动两次
        while tail != self.first:  # 当tail == first 说明只剩一个人
            for i in range(m-1):
                tail = tail.pNext
                self.first = self.first.pNext
            self.first = self.first.pNext
            tail.pNext = self.first
        print("最后留在圈圈中的人是:%d" % tail.no)

c = Child()
c.addChild(100)
c.showChild()
# 次数,第几个开始
c.countChild(150,20)
方式二

2.1 顺序表和链表的对比

 
2.2 线性结构的两种应用方式之栈

一种可以实现“先进后出”的存储结构

栈类似于一个箱子,先放进去的书,最后才能取出来,同理,后放进去的书,先取出来

 

栈的分类

静态栈

静态栈的核心是数组,类似于一个连续内存的数组,我们只能操作其栈顶元素

动态栈

动态栈的核心是链表

栈的算法

栈的算法主要是压栈和出栈两种操作的算法,下面我就用代码来实现一个简单的栈。

首先要明白以下思路:

  • 栈操作的是一个一个节点
  • 栈本身也是一种存储的数据结构
  • 栈有初始化压栈出栈判空遍历清空等主要方法
# 栈的实现

class Stack:
    """基于顺序表实现"""

    def __init__(self):
        self.__list = []

    def push(self, item):
        # 时间复杂度O(1)
        self.__list.append(item)
        # 时间复杂度O(n)
        # self.__list.insert(0,item)

    def pop(self):
        # 时间复杂度O(1)
        return self.__list.pop()
        # 时间复杂度O(n)
        # return self.__list.pop(0)

    def peek(self):
        if self.is_empty():
            return None
        return self.__list[-1]

    def is_empty(self):
        return not self.__list

    def size(self):
        return len(self.__list)


if __name__ == '__main__':
    stack_obj = Stack()
    stack_obj.push(1)
    stack_obj.push(2)
    stack_obj.push(3)
    print("empty:", stack_obj.is_empty())
    print("size:", stack_obj.size())
    print(stack_obj.pop())
    print(stack_obj.pop())
    print(stack_obj.pop())
基于顺序表实现

栈的实现

  • 函数调用
  • 浏览器的前进或者后退
  • 表达式求值
  • 内存分配...
def f1():
    print("f1")
    f2()
    
def f2():
    print("f2")
    f3()
    
def f3():
    print("f3")

f1()

# 对于这个程序来说
执行函数f1,将f1压栈,打印"f1",执行f2,将f2压栈并将控制权交予f2,打印"f2",执行f3,将f3进行压栈并将控制权交予f3,打印"f3",出栈,则为f3先出
函数调用
通过两个栈之间的入栈和出栈实现浏览器的前进和后退
浏览器的前进和后退
# 对于表达式:8+5*4-3

分为操作符栈和数据栈
先将数值8和5放入数据栈,+号放入操作符栈,然后将4放入数据栈,由于乘号的优先级大于加号,继续将*号放入操作符栈,随后由于-号的优先级小于乘号,所以先将5*4计算并将结果放入数据栈,-号再放入操作符栈

# 注意:操作符放入的条件是,优先级大于栈顶的操作符优先级
表达式求值

2.3 线性结构的两种应用方式之队列

定义

一种可以实现“先进先出”的数据结构

队列的分类

静态队列和链式队列

队列的算法

#  队列的实现

class Queue:
    """基于顺序表"""

    def __init__(self):
        self.__list = []

    def enqueue(self, item):
        # O(1)
        self.__list.append(item)
        # O(n)
        # self.__list.insert(0, item)

    def dequeue(self):
        # O(n)
        return self.__list.pop(0)
        # O(1)
        # return self.__list.pop()

    def is_empty(self):
        return not self.__list

    def size(self):
        return len(self.__list)


if __name__ == '__main__':
    queue_obj = Queue()
    queue_obj.enqueue(1)
    queue_obj.enqueue(2)
    queue_obj.enqueue(3)
    print("empty:", queue_obj.is_empty())
    print("size:", queue_obj.size())
    print(queue_obj.dequeue())
    print(queue_obj.dequeue())
    print(queue_obj.dequeue())
静态队列

队列的实际用途

所有和时间有关的操作都和队列有关

多个函数的调用

当有多个函数调用时,按照“先调用后返回”的原则,函数之间的信息传递和控制转移必须借助栈来实现,即系统将整个程序运行时所需要的数据空间安排在一个栈中,每当调用一个函数时,就在栈顶分配一个存储区,进行压栈操作,每当一个函数退出时,就释放他的存储区,即进行出栈操作,当前运行的函数永远在栈顶的位置

递归

一个函数自己或者间接调用自己

递归的应用

树和森林就是以递归的方式定义的
树和图的算范就是以递归的方式实现的
很多数学公式就是以递归的方式实现的(斐波那楔序列)

2.4 小结

数据结构:
从狭义的方面讲:

  • 数据结构就是为了研究数据的存储问题
  • 数据结构的存储包含两个方面:个体的存储 + 个体关系的存储

从广义方面来讲:

  • 数据结构既包含对数据的存储,也包含对数据的操作
  • 对存储数据的操作就是算法

算法
从狭义的方面讲:

  • 算法是和数据的存储方式有关的

从广义的方面讲:

  • 算法是和数据的存储方式无关,这就是泛型的思想

3 非线性结构

3.1 树

3.1.1 树的定义

我们可以简单的认为:

  • 树有且仅有一个根节点
  • 有若干个互不相交的子树,这些子树本身也是一颗树

通俗的定义:
1.树就是由节点和边组成的
2.每一个节点只能有一个父节点,但可以有多个子节点。但有一个节点例外,该节点没有父节点,此节点就称为根节点

3.1.2 树相关的专业术语

  • 节点
  • 父节点
  • 子节点
  • 子孙
  • 堂兄弟
  • 兄弟
  • 深度
    • 从根节点到最底层节点的层数被称为深度,根节点是第一层
  • 叶子节点
    • 没有子节点的节点
    • 子节点的个数
3.1.3 树的分类
  • 一般树
    • 任意一个节点的子节点的个数不受限制
  • 二叉树
    • 定义:任意一个节点的子节点的个数最多是两个,且子节点的位置不可更改
      • 满二叉树
        • 定义:在不增加层数的前提下,无法再多添加一个节点的二叉树
      • 完全二叉树
        • 定义:只是删除了满二叉树最底层最右边连续的若干个节点
      • 一般二叉树
  • 森林
    • n个互不相交的数的集合

3.1.4 树的操作

如何把一个非线性结构的数据转换成一个线性结构的数据存储起来?

一般树的存储

1.双亲表示法(求父节点方便)

2.孩子表示法(求孩子方便)

3.双亲孩子表示法(求孩子和父亲都很方便)

4.二叉树表示法(即把一般树转换为二叉树,按照二叉树的方法存储)

具体的方法,设法保证

1.左指针域指向它的第一个孩子

2.右指针域指向它的下一个兄弟

二叉树操作

连续存储 (完全二叉树,数组方式进行存储)

优点:查找某个节点的父节点和子节点非常的快
缺点:耗用内存空间过大
转化的方法:先序 中序 后序

链式存储 (链表存储)
data区域 左孩子区域 右孩子区域
森林的操作

把所有的树转化成二叉树,方法同一般树的转化

3.1.5 二叉树的具体操作

1.二叉树的先序遍历[先访问根节点]

先访问根节点

再先序遍历左子树

最后先序遍历右子树

2.二叉树的中序遍历 [中间访问根节点]

先中序遍历左子树’

再访问根节点

最后中序遍历右子树

3.二叉树的后序遍历 [最后访问根节点]

先后序遍历左子树

再后序遍历右子树

最后访问根节点

已知先序和中序,如何求出后序?

例1:
先序:ABCDEFGH
中序:BDCEAFHG
后序:DECBHGFA
View Code

例2:

先序:ABDGHCEFI
中序:GDHBAECIF
后序:GHDBEIFCA
View Code

已知中序和后序,如何求出先序?

中序:BDCEAFHG
后序:DECBHGFA
先序:ABCDEFGH
View Code

3.1.6 树的应用

  • 树是数据库中数据组织的一种重要形式
  • 操作系统子父进程的关系本身就是一颗树
  • 面型对象语言中类的继承关系

4 总结

数据结构研究的就是数据的存储和数据的操作的一门学问

数据的存储又分为两个部分:

  • 个体的存储
  • 个体关系的存储

从某个角度而言,数据存储最核心的就是个体关系如何进行存储
个体的存储可以忽略不计

5 数据结构与算法学习的相关书籍

 

posted @ 2019-01-28 13:34  风中琉璃  阅读(147)  评论(0)    收藏  举报